#!/usr/bin/env python3
# Copyright (C) 2007-2012 Michael Foord & the mock team
# E-mail: fuzzyman AT voidspace DOT org DOT uk
# http://www.voidspace.org.uk/python/mock/

from tests.support import unittest2, inPy3k

try:
  str
except NameError:
  # Python 3
  str = str
  long = int

import inspect
import sys
from mock import Mock, MagicMock, _magics


class TestMockingMagicMethods(unittest2.TestCase):
  def test_deleting_magic_methods(self):
    mock = Mock()
    self.assertFalse(hasattr(mock, "__getitem__"))

    mock.__getitem__ = Mock()
    self.assertTrue(hasattr(mock, "__getitem__"))

    del mock.__getitem__
    self.assertFalse(hasattr(mock, "__getitem__"))

  def test_magicmock_del(self):
    mock = MagicMock()
    # before using getitem
    del mock.__getitem__
    self.assertRaises(TypeError, lambda: mock["foo"])

    mock = MagicMock()
    # this time use it first
    mock["foo"]
    del mock.__getitem__
    self.assertRaises(TypeError, lambda: mock["foo"])

  def test_magic_method_wrapping(self):
    mock = Mock()

    def f(self, name):
      return self, "fish"

    mock.__getitem__ = f
    self.assertFalse(mock.__getitem__ is f)
    self.assertEqual(mock["foo"], (mock, "fish"))
    self.assertEqual(mock.__getitem__("foo"), (mock, "fish"))

    mock.__getitem__ = mock
    self.assertTrue(mock.__getitem__ is mock)

  def test_magic_methods_isolated_between_mocks(self):
    mock1 = Mock()
    mock2 = Mock()

    mock1.__iter__ = Mock(return_value=iter([]))
    self.assertEqual(list(mock1), [])
    self.assertRaises(TypeError, lambda: list(mock2))

  def test_repr(self):
    mock = Mock()
    self.assertEqual(repr(mock), f"<Mock id='{id(mock)}'>")
    mock.__repr__ = lambda s: "foo"
    self.assertEqual(repr(mock), "foo")

  def test_str(self):
    mock = Mock()
    self.assertEqual(str(mock), object.__str__(mock))
    mock.__str__ = lambda s: "foo"
    self.assertEqual(str(mock), "foo")

  @unittest2.skipIf(inPy3k, "no unicode in Python 3")
  def test_unicode(self):
    mock = Mock()
    self.assertEqual(str(mock), str(str(mock)))

    mock.__unicode__ = lambda s: str("foo")
    self.assertEqual(str(mock), str("foo"))

  def test_dict_methods(self):
    mock = Mock()

    self.assertRaises(TypeError, lambda: mock["foo"])

    def _del():
      del mock["foo"]

    def _set():
      mock["foo"] = 3

    self.assertRaises(TypeError, _del)
    self.assertRaises(TypeError, _set)

    _dict = {}

    def getitem(s, name):
      return _dict[name]

    def setitem(s, name, value):
      _dict[name] = value

    def delitem(s, name):
      del _dict[name]

    mock.__setitem__ = setitem
    mock.__getitem__ = getitem
    mock.__delitem__ = delitem

    self.assertRaises(KeyError, lambda: mock["foo"])
    mock["foo"] = "bar"
    self.assertEqual(_dict, {"foo": "bar"})
    self.assertEqual(mock["foo"], "bar")
    del mock["foo"]
    self.assertEqual(_dict, {})

  def test_numeric(self):
    original = mock = Mock()
    mock.value = 0

    self.assertRaises(TypeError, lambda: mock + 3)

    def add(self, other):
      mock.value += other
      return self

    mock.__add__ = add
    self.assertEqual(mock + 3, mock)
    self.assertEqual(mock.value, 3)

    del mock.__add__

    def iadd(mock):
      mock += 3

    self.assertRaises(TypeError, iadd, mock)
    mock.__iadd__ = add
    mock += 6
    self.assertEqual(mock, original)
    self.assertEqual(mock.value, 9)

    self.assertRaises(TypeError, lambda: 3 + mock)
    mock.__radd__ = add
    self.assertEqual(7 + mock, mock)
    self.assertEqual(mock.value, 16)

  @unittest2.skipIf(inPy3k, "no truediv in Python 3")
  def test_truediv(self):
    mock = MagicMock()
    mock.__truediv__.return_value = 6

    context = {"mock": mock}
    code = "from __future__ import division\nresult = mock / 7\n"
    exec(code, context)
    self.assertEqual(context["result"], 6)

    mock.__rtruediv__.return_value = 3
    code = "from __future__ import division\nresult = 2 / mock\n"
    exec(code, context)
    self.assertEqual(context["result"], 3)

  @unittest2.skipIf(not inPy3k, "truediv is available in Python 2")
  def test_no_truediv(self):
    self.assertRaises(AttributeError, getattr, MagicMock(), "__truediv__")
    self.assertRaises(AttributeError, getattr, MagicMock(), "__rtruediv__")

  def test_hash(self):
    mock = Mock()
    # test delegation
    self.assertEqual(hash(mock), Mock.__hash__(mock))

    def _hash(s):
      return 3

    mock.__hash__ = _hash
    self.assertEqual(hash(mock), 3)

  def test_nonzero(self):
    m = Mock()
    self.assertTrue(bool(m))

    nonzero = lambda s: False
    if not inPy3k:
      m.__nonzero__ = nonzero
    else:
      m.__bool__ = nonzero

    self.assertFalse(bool(m))

  def test_comparison(self):
    # note: this test fails with Jython 2.5.1 due to a Jython bug
    #       it is fixed in jython 2.5.2
    if not inPy3k:
      # incomparable in Python 3
      self.assertEqual(Mock() < 3, object() < 3)
      self.assertEqual(Mock() > 3, object() > 3)
      self.assertEqual(Mock() <= 3, object() <= 3)
      self.assertEqual(Mock() >= 3, object() >= 3)
    else:
      self.assertRaises(TypeError, lambda: MagicMock() < object())
      self.assertRaises(TypeError, lambda: object() < MagicMock())
      self.assertRaises(TypeError, lambda: MagicMock() < MagicMock())
      self.assertRaises(TypeError, lambda: MagicMock() > object())
      self.assertRaises(TypeError, lambda: object() > MagicMock())
      self.assertRaises(TypeError, lambda: MagicMock() > MagicMock())
      self.assertRaises(TypeError, lambda: MagicMock() <= object())
      self.assertRaises(TypeError, lambda: object() <= MagicMock())
      self.assertRaises(TypeError, lambda: MagicMock() <= MagicMock())
      self.assertRaises(TypeError, lambda: MagicMock() >= object())
      self.assertRaises(TypeError, lambda: object() >= MagicMock())
      self.assertRaises(TypeError, lambda: MagicMock() >= MagicMock())

    mock = Mock()

    def comp(s, o):
      return True

    mock.__lt__ = mock.__gt__ = mock.__le__ = mock.__ge__ = comp
    self.assertTrue(mock < 3)
    self.assertTrue(mock > 3)
    self.assertTrue(mock <= 3)
    self.assertTrue(mock >= 3)

  def test_equality(self):
    for mock in Mock(), MagicMock():
      self.assertEqual(mock == mock, True)
      self.assertIsInstance(mock == mock, bool)
      self.assertEqual(mock != mock, False)
      self.assertIsInstance(mock != mock, bool)
      self.assertEqual(mock == object(), False)
      self.assertEqual(mock != object(), True)

      def eq(self, other):
        return other == 3

      mock.__eq__ = eq
      self.assertTrue(mock == 3)
      self.assertFalse(mock == 4)

      def ne(self, other):
        return other == 3

      mock.__ne__ = ne
      self.assertTrue(mock != 3)
      self.assertFalse(mock != 4)

    mock = MagicMock()
    mock.__eq__.return_value = True
    self.assertIsInstance(mock == 3, bool)
    self.assertEqual(mock == 3, True)

    mock.__ne__.return_value = False
    self.assertIsInstance(mock != 3, bool)
    self.assertEqual(mock != 3, False)

  def test_len_contains_iter(self):
    mock = Mock()

    self.assertRaises(TypeError, len, mock)
    self.assertRaises(TypeError, iter, mock)
    self.assertRaises(TypeError, lambda: "foo" in mock)

    mock.__len__ = lambda s: 6
    self.assertEqual(len(mock), 6)

    mock.__contains__ = lambda s, o: o == 3
    self.assertTrue(3 in mock)
    self.assertFalse(6 in mock)

    mock.__iter__ = lambda s: iter("foobarbaz")
    self.assertEqual(list(mock), list("foobarbaz"))

  def test_magicmock(self):
    mock = MagicMock()

    mock.__iter__.return_value = iter([1, 2, 3])
    self.assertEqual(list(mock), [1, 2, 3])

    name = "__nonzero__"
    other = "__bool__"
    if inPy3k:
      name, other = other, name
    getattr(mock, name).return_value = False
    self.assertFalse(hasattr(mock, other))
    self.assertFalse(bool(mock))

    for entry in _magics:
      self.assertTrue(hasattr(mock, entry))
    self.assertFalse(hasattr(mock, "__imaginery__"))

  def test_magic_mock_equality(self):
    mock = MagicMock()
    self.assertIsInstance(mock == object(), bool)
    self.assertIsInstance(mock != object(), bool)

    self.assertEqual(mock == object(), False)
    self.assertEqual(mock != object(), True)
    self.assertEqual(mock == mock, True)
    self.assertEqual(mock != mock, False)

  def test_magicmock_defaults(self):
    mock = MagicMock()
    self.assertEqual(int(mock), 1)
    self.assertEqual(complex(mock), 1j)
    self.assertEqual(float(mock), 1.0)
    self.assertEqual(int(mock), int(1))
    self.assertNotIn(object(), mock)
    self.assertEqual(len(mock), 0)
    self.assertEqual(list(mock), [])
    self.assertEqual(hash(mock), object.__hash__(mock))
    self.assertEqual(str(mock), object.__str__(mock))
    self.assertEqual(str(mock), object.__str__(mock))
    self.assertIsInstance(str(mock), str)
    self.assertTrue(bool(mock))
    if not inPy3k:
      self.assertEqual(oct(mock), "1")
    else:
      # in Python 3 oct and hex use __index__
      # so these tests are for __index__ in py3k
      self.assertEqual(oct(mock), "0o1")
    self.assertEqual(hex(mock), "0x1")
    # how to test __sizeof__ ?

  @unittest2.skipIf(inPy3k, "no __cmp__ in Python 3")
  def test_non_default_magic_methods(self):
    mock = MagicMock()
    self.assertRaises(AttributeError, lambda: mock.__cmp__)

    mock = Mock()
    mock.__cmp__ = lambda s, o: 0

    self.assertEqual(mock, object())

  def test_magic_methods_and_spec(self):
    class Iterable(object):
      def __iter__(self):
        pass

    mock = Mock(spec=Iterable)
    self.assertRaises(AttributeError, lambda: mock.__iter__)

    mock.__iter__ = Mock(return_value=iter([]))
    self.assertEqual(list(mock), [])

    class NonIterable(object):
      pass

    mock = Mock(spec=NonIterable)
    self.assertRaises(AttributeError, lambda: mock.__iter__)

    def set_int():
      mock.__int__ = Mock(return_value=iter([]))

    self.assertRaises(AttributeError, set_int)

    mock = MagicMock(spec=Iterable)
    self.assertEqual(list(mock), [])
    self.assertRaises(AttributeError, set_int)

  def test_magic_methods_and_spec_set(self):
    class Iterable(object):
      def __iter__(self):
        pass

    mock = Mock(spec_set=Iterable)
    self.assertRaises(AttributeError, lambda: mock.__iter__)

    mock.__iter__ = Mock(return_value=iter([]))
    self.assertEqual(list(mock), [])

    class NonIterable(object):
      pass

    mock = Mock(spec_set=NonIterable)
    self.assertRaises(AttributeError, lambda: mock.__iter__)

    def set_int():
      mock.__int__ = Mock(return_value=iter([]))

    self.assertRaises(AttributeError, set_int)

    mock = MagicMock(spec_set=Iterable)
    self.assertEqual(list(mock), [])
    self.assertRaises(AttributeError, set_int)

  def test_setting_unsupported_magic_method(self):
    mock = MagicMock()

    def set_setattr():
      mock.__setattr__ = lambda self, name: None

    self.assertRaisesRegex(
      AttributeError,
      "Attempting to set unsupported magic method '__setattr__'.",
      set_setattr,
    )

  def test_attributes_and_return_value(self):
    mock = MagicMock()
    attr = mock.foo

    def _get_type(obj):
      # the type of every mock (or magicmock) is a custom subclass
      # so the real type is the second in the mro
      return type(obj).__mro__[1]

    self.assertEqual(_get_type(attr), MagicMock)

    returned = mock()
    self.assertEqual(_get_type(returned), MagicMock)

  def test_magic_methods_are_magic_mocks(self):
    mock = MagicMock()
    self.assertIsInstance(mock.__getitem__, MagicMock)

    mock[1][2].__getitem__.return_value = 3
    self.assertEqual(mock[1][2][3], 3)

  def test_magic_method_reset_mock(self):
    mock = MagicMock()
    str(mock)
    self.assertTrue(mock.__str__.called)
    mock.reset_mock()
    self.assertFalse(mock.__str__.called)

  @unittest2.skipUnless(
    sys.version_info[:2] >= (2, 6), "__dir__ not available until Python 2.6 or later"
  )
  def test_dir(self):
    # overriding the default implementation
    for mock in Mock(), MagicMock():

      def _dir(self):
        return ["foo"]

      mock.__dir__ = _dir
      self.assertEqual(dir(mock), ["foo"])

  @unittest2.skipIf("PyPy" in sys.version, "This fails differently on pypy")
  def test_bound_methods(self):
    m = Mock()

    # XXXX should this be an expected failure instead?

    # this seems like it should work, but is hard to do without introducing
    # other api inconsistencies. Failure message could be better though.
    m.__iter__ = [3].__iter__
    self.assertRaises(TypeError, iter, m)

  def test_magic_method_type(self):
    class Foo(MagicMock):
      pass

    foo = Foo()
    self.assertIsInstance(foo.__int__, Foo)

  def test_descriptor_from_class(self):
    m = MagicMock()
    type(m).__str__.return_value = "foo"
    self.assertEqual(str(m), "foo")

  def test_iterable_as_iter_return_value(self):
    m = MagicMock()
    m.__iter__.return_value = [1, 2, 3]
    self.assertEqual(list(m), [1, 2, 3])
    self.assertEqual(list(m), [1, 2, 3])

    m.__iter__.return_value = iter([4, 5, 6])
    self.assertEqual(list(m), [4, 5, 6])
    self.assertEqual(list(m), [])


if __name__ == "__main__":
  unittest2.main()
